#include <stdio.h>
#include <errno.h>
#include <string.h>

typedef struct Student {
    char name[25];
    int age;
} Student;

// 定义带参数的宏，注意带参数的宏，一定要在宏体中把宏参数用()包起来
// 宏和函数调用的区别：宏就是文本替换，没有函数调用开销，运行时性能更佳
// 但是宏用的太多，会导致程序的体积变大
// 宏的另一个缺点就是不容易调试。
#define CIRCLE_AREA(r) (3.1415926 * (r) * (r))

// 注意将宏参数做为字符串的写法
#define AS_STRING(x) #x

// 用##可以把宏参数拼接起来，实际使用时要小心
#define CONCAT(x, y) x##y

// 定义内联函数。内联函数兼具宏和函数的优点
// 注意：内联函数并不是百分之百就会被展开，如果函数太复杂，编译器会当成普通函数
inline double circle_area(double radius) {
    return radius * radius * 3.14159;
}

void save_students(Student *students, unsigned len) {
    FILE* fp = fopen("/Users/wangqiang/Desktop/students", "a");
    if (NULL == fp) {
        printf("error open file %s\n", strerror(errno));
        return;
    }
    for (int i = 0; i < len; i++) {
        fprintf(fp, "%s %d\n", students[i].name, students[i].age);
    }
    fclose(fp);
}

void read_students(Student *students, unsigned len) {
    FILE* fp = fopen("/Users/wangqiang/Desktop/students", "r");
    if (NULL == fp) {
        printf("error open file %s\n", strerror(errno));
        return;
    }
    for (int i = 0; i < len; i++) {
        fscanf(fp, "%s %d\n", students[i].name, &students[i].age);
    }
    fclose(fp);
}

int main(int argc, char** argv) {
    // 调用带参数宏
    CIRCLE_AREA(2);
    // 注意宏拼接的效果，拼接后的效果就是2 + 33 + 4
    printf("%d\n", CONCAT(2 + 3, 3 + 4)); 

    // fopen打开文件，第一个参数为文件路径，第二个参数是读写模式
    FILE* fp = fopen("/Users/wangqiang/Desktop/tt102", "a");
    if (NULL == fp) {
        // 如果出错，C会将错误码保存在内置变脸errno中，可以打印错误信息
        printf("error open file %s\n", strerr(errno));
        return -1;
    }

    // 通过fgetc一次读一个字节出来，这个用的比较少，因为会造成大量磁盘IO。
    for (char ch = fgetc(fp); ch != EOF; ch = fgetc(fp)) {
        printf("%c", ch);
    }
    // 通过fputc一次写一个字节，和fgetc一样，因为会增加磁盘IO数，所以不常用。
    if(!fputc('z', fp)) {
        printf("error fputc\n");
        return -1;
    }

    // 用fgets可以读取指定字节
    char buf[1024];
    while (fgets(buf, 1024, fp) != NULL) {
        printf("%s\n", buf);
    }
    // 用fputs向文件中写入任意字节
    if (fputs("good morning", fp) < 0 ) {
        printf("fputs error");
        return -1;
    }

    // fprintf可以把结构体写入到文件中，和printf用法非常相似。
    Student stud = {"Jack", 23};
    if (fprintf(fp, "%s%d", stud.name, stud.age) < 0 ) {
        printf("fprintf error");
    }

    // 用fwrite可以写入二进制数据到文件
    int arr[] = {101, 102, 103, 104};
    // 第一个参数是待写入数据指针，第二个参数为每个块的大小，第三个参数为写入块数。
    if (!fwrite(arr, 4, 4, fp)) {
        printf("fwrite error\n");
        return -1;
    }
    // 用fread可以从二进制文件读入数据
    int arr[4];
    printf("%d\n", fread(arr, 4, 4, fp));
    for (int i = 0; i < 4; i++) {
        printf("%d\n", arr[i]);
    }

    // 用完以后记得关闭文件
    fclose(fp);
}